
open Chipmunk.Low_level

type t =
	{         space        : cpSpace
	; mutable playersID    : int
	; mutable players      : Player.t Types.IntMap.t
	;         playersMutex : Mutex.t
	; mutable objectsID    : int
	; mutable objects      : Object.t Types.IntMap.t
	;         objectsMutex : Mutex.t
	}

let cpv = vec2d
let cpvi (x, y) = { cp_x = float x; cp_y = float y }

let dt = Config.dt
let steps = Config.steps

let registerPlayer ge player = 
	let () = Mutex.lock ge.playersMutex in
	let result = ge.playersID in
	let () = ge.players <- Types.IntMap.add result player ge.players in
	let () = ge.playersID <- ge.playersID + 1 in
	let () = Mutex.unlock ge.playersMutex in
	result
let players ge = 
	let () = Mutex.lock ge.playersMutex in
	let result = ge.players in
	let () = Mutex.unlock ge.playersMutex in
	result
let registerObject ge objct = 
	let () = Mutex.lock ge.objectsMutex in
	let result = ge.objectsID in
	let () = ge.objects <- Types.IntMap.add result objct ge.objects in
	let () = ge.objectsID <- ge.objectsID + 1 in
	let () = Mutex.unlock ge.objectsMutex in
	result
let objects ge = 
	let () = Mutex.lock ge.objectsMutex in
	let result = ge.objects in
	let () = Mutex.unlock ge.objectsMutex in
	result

let createFrame ge = 
    let space = ge.space in
    let frameBody = cpBodyNew infinity infinity in
    (* no cpSpaceAddBody -> no gravity for frame *)
    let frameShape1 = cpSegmentShapeNew frameBody (cpvi(1,1)) (cpvi(1,0)) 0.0 in
    let frameShape2 = cpSegmentShapeNew frameBody (cpvi(1,0)) (cpvi(0,0)) 0.0 in
    let frameShape3 = cpSegmentShapeNew frameBody (cpvi(0,0)) (cpvi(0,1)) 0.0 in
    let frameShape4 = cpSegmentShapeNew frameBody (cpvi(0,1)) (cpvi(1,1)) 0.0 in
    let () = cpShapeSetElasticity frameShape1 1.0 in
    let () = cpShapeSetElasticity frameShape2 1.0 in
    let () = cpShapeSetElasticity frameShape3 1.0 in
    let () = cpShapeSetElasticity frameShape4 1.0 in
    let () = cpShapeSetFriction frameShape1 1.0 in
    let () = cpShapeSetFriction frameShape2 1.0 in
    let () = cpShapeSetFriction frameShape3 1.0 in
    let () = cpShapeSetFriction frameShape4 1.0 in
    let () = cpSpaceAddStaticShape space frameShape1 in
    let () = cpSpaceAddStaticShape space frameShape2 in
    let () = cpSpaceAddStaticShape space frameShape3 in
    let () = cpSpaceAddStaticShape space frameShape4 in
    ()  

let init mapFilename = 
	let () = cpInitChipmunk () in
	let () = cpResetShapeIdCounter () in
	let space = cpSpaceNew () in
	let () = cpSpaceResizeStaticHash space 20.0 999 in (* TODO check parameters *)
	let () = cpSpaceSetGravity space (cpv (0.0 , -.Config.gravity)) in
	let () = set_cp_collision_slop 1e-4 in
	let ge = 
		{ space
		; playersID = 0
		; players = Types.IntMap.empty
		; playersMutex = Mutex.create ()
		; objectsID = 0
		; objects = Types.IntMap.empty
		; objectsMutex = Mutex.create ()
		}
	in 
	let () = createFrame ge in
	ge

let step ge = 
	let space = ge.space in
	let () = Types.IntMap.iter 
		(fun key player -> Player.step player) 
		(players ge) 
	in
	let () = Types.IntMap.iter
		(fun key objct -> Object.step objct)
		(objects ge)
	in
	let dt = dt /. (float steps) in
	let () = 
		for ii = 0 to pred steps do	
			cpSpaceStep ~space ~dt
		done
	in ()

let new_player ge =
	let player = Player.create ge.space in
	let id = registerPlayer ge player in
	id
	
let unregister_player ge id = (* TODO *) ()

let rec player_command ge player cmd = 
	let player = Types.IntMap.find player (players ge) in
	match cmd with
	| Types.Data.String "getObjects" ->
		Types.Data.List (List.map (fun (_, v) -> Object.show v)
				(Types.IntMap.bindings (objects ge)))
	| Types.Data.String "getPlayers" ->
		Types.Data.List (List.map (fun (_, v) -> Player.show v)
				(Types.IntMap.bindings (players ge)))
	| _ -> Player.command player cmd

let rec engine_command ge cmd = match cmd with
	| Types.Data.List [
		Types.Data.String "Segment";
		Types.Data.Float start_x;
		Types.Data.Float start_y;
		Types.Data.Float end_x;
		Types.Data.Float end_y] -> 
			let obj = Object.Segment.create ge.space start_x start_y end_x end_y in
			let _ = registerObject ge (Object.Segment obj) in
			Types.Data.List []
	| Types.Data.List [
		Types.Data.String "Box";
		Types.Data.Float center_x;
		Types.Data.Float center_y;
		Types.Data.Float size;
		Types.Data.Float mass] -> 
			let obj = Object.Box.create ge.space center_x center_y size mass in
			let _ = registerObject ge (Object.Box obj) in
			Types.Data.List []
	| _ -> Types.Data.List [Types.Data.String "Unknown command!"; cmd]

let rec admin_command ge cmd = match cmd with
	| Types.Data.List [
		Types.Data.String "repeat";
		Types.Data.Float x;
		cmd' ] -> 
			let n = int_of_float x in
			let () = for ii = 1 to n do
					let _ = admin_command ge cmd' in ()
				done
			in Types.Data.List []
	| _ -> engine_command ge cmd
	

